<?php
abstract class Dig_Model_Abstract
{
    public $id;
    public $meta_date_created;
    public $meta_date_updated;
    public $meta_parent_id;
    protected $_dbTable;
    protected $_db = 'main';
    protected $_table = 'data';
    protected $_relationships = array('has_many' => array() , 'belongs_to' => array());
    protected $_readOnly = false;
    const HIDDEN_PROPERTY = 'this property is hidden and not accessible';
    /**
     * you can optionally pass the constructor an id
     * which it will load
     *
     * @param int $id
     */
    public function __construct ($id = null)
    {
        if (empty($this->_table)) {
            throw new Dig_Model_Exception('Invalid model: the required table property is not set.');
        }
        $this->_dbTable = new Zend_Db_Table($this->_table);
        if (null != $id) {
            $row = $this->_dbTable->find($id)->current();
            $this->loadRow($row);
        }
        $this->init();
    }
    public function readOnly ($flag = true)
    {
        $this->_readOnly = $flag;
    }
    public function init ()
    {}
    protected function _belongsTo ($key, $class, $column)
    {
        $relationship = array('column' => $column , 'class' => $class);
        $this->_relationships['belongs_to'][$key] = $relationship;
    }
    protected function _hasMany ($key, $class, $column)
    {
        $relationship = array('column' => $column , 'class' => $class);
        $this->_relationships['has_many'][$key] = $relationship;
    }
    /**
     * the load row method loads a Digitalus_Model with a Zend_Db_Table_Row
     *
     * @param Zend_Db_Table_Row $row
     */
    public function loadRow (Zend_Db_Table_Row $row = null)
    {
        if ($row == null) {
            throw new Dig_Model_Exception("There was an error loading the item you requested");
        }
        $data = $row->toArray();
        // validate the virtual table and namespace of the model
        if ($data['digitalus_table'] != $this->_digitalusTable) {
            throw new Dig_Model_Exception('Invalid virtual table: ' . $data['digitalus_table']);
        }
        unset($data['digitalus_table']);
        if ($data['digitalus_namespace'] != $this->_digitalusNamespace) {
            throw new Dig_Model_Exception('Unable to cast type: ' . $data['digitalus_namespace'] . ' as ' . get_class($this));
        }
        unset($data['digitalus_namespace']);
        $this->loadArray($data);
    }
    /**
     * the loadArray method loads the model with an array of data
     * it does not support overloading, so you can only load 
     * defined public properties
     *
     * @param array $data
     */
    public function loadArray ($data = array())
    {
        $propertyArray = $this->_getPublicProperties();
        // these are the static fields
        if (isset($data['id'])) {
            $this->id = $data['id'];
            unset($data['id']);
        }
        foreach ($data as $key => $value) {
            if (array_key_exists($key, $propertyArray)) {
                $this->$key = $value;
            }
        }
    }
    /**
     * the save method saves the current model to the database
     * you can optionally pass it an array of data
     * which it will load prior to saving
     * 
     * if the model's id is set it will update the row
     * otherwise it will insert the row
     *
     * @param array $data
     */
    public function save ($data = null)
    {
        if ($this->_readOnly == true) {
            throw new Dig_Model_Exception('The model you are attempting to update is read only.');
        }
        if (is_array($data)) {
            $this->loadArray($data);
        }
        $propertyArray = $this->_getPublicProperties();
        // if the id is set then this is an update rather than an insert
        if ($propertyArray['id'] != null) {
            $rowId = $propertyArray['id'];
            $insert = false;
        } else {
            $insert = true;
        }
        unset($propertyArray['id']);
        // load the table data
        $tableCols = $this->_dbTable->info('cols');
        foreach ($propertyArray as $key => $value) {
            if (! in_array($key, $tableCols)) {
                $this->_addColumn($key);
            }
            //create the method name
            $method = Zend_Filter::filterStatic($key, 'Word_UnderscoreToCamelCase');
            $methodName = '_set' . $method;
            if (method_exists($this, $methodName)) {
                $cleanData[$key] = $this->$methodName();
            } else {
                $cleanData[$key] = $value;
            }
        }
        // set the protected properties
        $cleanData['digitalus_table'] = $this->_digitalusTable;
        $cleanData['digitalus_namespace'] = $this->_digitalusNamespace;
        // update the database
        if (true == $insert) {
            $cleanData['meta_date_created'] = time();
            $this->_dbTable->insert($cleanData);
            $this->id = $this->_dbTable->getAdapter()->lastInsertId($this->_table);
        } else {
            $cleanData['meta_date_updated'] = time();
            $where = $this->_dbTable->getAdapter()->quoteInto('id = ?', intval($rowId));
            $this->_dbTable->update($cleanData, $where);
        }
    }
    public function toArray ()
    {
        return $this->_getPublicProperties();
    }
    public function hideProperty ($property)
    {
        $this->$property = self::HIDDEN_PROPERTY;
    }
    /**
     * this method deletes a row from the database
     * you can either pass it an id to delete or if this is not set
     * it will delete the current row
     *
     * @param int $id
     */
    public function delete ($id = null)
    {
        if (null == $id) {
            $id = $this->id;
        }
        $where = $this->_dbTable->getAdapter()->quoteInto('id = ?', intval($id));
        $this->_dbTable->delete($where);
    }
    /**
     * this method returns the db select object with the namespace set
     *
     */
    public function select ()
    {
        $select = $this->_dbTable->select();
        $select->where('digitalus_table = ?', $this->_digitalusTable);
        $select->where('digitalus_namespace = ?', $this->_digitalusNamespace);
        return $select;
    }
    /**
     * the find method wraps the Zend_Db_Table find method
     * but returns a new instance of Digitalus_Model
     *
     * @param int $id
     * @return Digitalus_Model
     */
    public function find ($id)
    {
        $row = $this->_dbTable->find($id)->current();
        if ($row) {
            $model = clone ($this);
            $model->resetProperties();
            $model->loadRow($row);
            return $model;
        }
        return false;
    }
    /**
     * the fetchRow method wraps the Zend_Db_Table fetchRow method
     * but returns a new instance of Digitalus_Model
     *
     * @param Zend_Db_Select
     * @return Digitalus_Model
     */
    public function fetchRow (Zend_Db_Select $select = null)
    {
        if (null == $select) {
            $select = $this->select();
        }
        $row = $this->_dbTable->fetchRow($select);
        if ($row) {
            $model = clone ($this);
            $model->resetProperties();
            $model->loadRow($row);
            return $model;
        }
        return false;
    }
    /**
     * the fetchAll method wraps the Zend_Db_Table fetchAll method
     * but returns an array of new instances of Digitalus_Model
     *
     * @param Zend_Db_Select
     * @return Digitalus_Model
     */
    public function fetchAll (Zend_Db_Select $select = null)
    {
        if (null == $select) {
            $select = $this->select();
        }
        $result = $this->_dbTable->fetchAll($select);
        if ($result->count() > 0) {
            foreach ($result as $row) {
                $model = clone ($this);
                $model->resetProperties();
                $model->loadRow($row);
                $resultSet[] = $model;
            }
            return $resultSet;
        }
    }
    /**
     * this method returns an associative array of all of the public properties
     * of the class
     *
     * @return array
     */
    protected function _getPublicProperties ()
    {
        $propertyArray = array();
        $properties = $this->_getProperties();
        foreach ($properties as $property) {
            if ($property->isPublic() && $property->getValue($this) != self::HIDDEN_PROPERTY) {
                $propertyArray[$property->getName()] = $property->getValue($this);
            }
        }
        return $propertyArray;
    }
    /**
     * this method wraps the Zend_Reflection_Class getProperties() method
     *
     * @return array
     */
    protected function _getProperties ()
    {
        $class = new Zend_Reflection_Class($this);
        return $class->getProperties();
    }
    /**
     * the reset method resets all of the current instance's public properties
     *
     */
    public function resetProperties ()
    {
        $properties = $this->_getPublicProperties();
        foreach (array_keys($properties) as $property) {
            $this->$property = null;
        }
    }
    /**
     * this method alters the underlying table, adding the column
     * @todo this should be refactored and moved to an abstraction layer
     *
     * @param string $column
     */
    protected function _addColumn ($column)
    {
        $stmt = sprintf('ALTER TABLE "%s"."%s" ADD COLUMN "%s" TEXT', $this->_db, $this->_table, $column);
        $this->_dbTable->getAdapter()->query($stmt);
    }
    public function __call ($name, $arguments)
    {
        if (substr($name, 0, 3) == 'get') {
            $method = str_replace('get', '', $name);
            $method = strtolower(Zend_Filter::filterStatic($method, 'Word_CamelCaseToUnderscore'));
            if (array_key_exists($method, $this->_relationships['belongs_to'])) {
                $class = $this->_relationships['belongs_to'][$method]['class'];
                $column = $this->_relationships['belongs_to'][$method]['column'];
                return new $class($this->$column);
            } elseif (array_key_exists($method, $this->_relationships['has_many'])) {
                $class = $this->_relationships['has_many'][$method]['class'];
                $column = $this->_relationships['has_many'][$method]['column'];
                $model = new $class();
                $select = $model->select();
                $select->where($column . ' = ?', $this->id);
                return $model->fetchAll($select);
            }
        }
    }
}